#include "semant.h"
#include "table.h"
#include "symbol.h"
#include "env.h"
#include "errormsg.h"
#include "absyn.h"
#include "translate.h"
#include "util.h"
#include "temp.h"
#include "tree.h"
#include "escape.h"
#include <string.h>


T_stmList SEM_transProg(A_exp root)
{
    S_table venv = S_empty();
    S_table tenv = S_empty();
    E_tenter(tenv, S_Symbol("int"), Ty_Int());
    E_tenter(tenv, S_Symbol("string"), Ty_String());

    assert(root);
    Esc_findEscape(root);     // find the escape variables
    SEM_transExp(venv, tenv, Tr_outermost(),root, NULL);
    return Tr_getStmList();
};


struct expty expTy(Tr_exp exp, Ty_ty ty)
{
    struct expty e;
    e.exp = exp;
    e.ty = ty;
    return e;
}

// find the actual type of a namety
Ty_ty actualType(Ty_ty ty)
{
    Ty_ty itr = ty;
    while (itr->kind == Ty_name)
        itr = itr->u.name.ty;
    return itr;
}

Ty_ty SEM_transTy(S_table tenv, A_ty a)
{
    switch (a->kind)
    {
    case A_nameTy:
    {
        Ty_ty t = S_look(tenv, a->u.name.ty);
        if (!t)
        {
            EM_error(a->pos, "Invalid type");
            return Ty_Nil();
        }
        return t;
    }
    case A_arrayTy:
    {
        Ty_ty t = S_look(tenv, a->u.array.type);
        Ty_ty newt = checked_malloc(sizeof(*newt));
        Ty_subList tsubNew, tsubOld = NULL;
        A_subList asub = a->u.array.size;
        if (!t)
        {
            EM_error(a->pos, "Invalid type");
            return Ty_Nil();
        }

        assert(asub);

        while(asub)    // copy the A_subList to Ty_subList
        {
            tsubNew = checked_malloc(sizeof(*tsubNew));
            tsubNew->data = asub->data->u.intt;
            tsubNew->next = NULL;
            if (tsubOld != NULL)
                tsubOld->next = tsubNew;
            else
                newt->u.array.size = tsubNew;
            tsubOld = tsubNew;
            asub = asub->next;
        }
        newt->kind = Ty_array;
        newt->u.array.type = t;
        newt->u.array.dim = a->u.array.dim;
        return newt;
    }
    case A_recordTy:
    {
        Ty_ty t;
        Ty_ty newt = checked_malloc(sizeof(*newt));
        Ty_fieldList tfieldsNew, tfieldsOld = NULL;
        Ty_field tfield= NULL;
        A_fieldList afields = a->u.record.record;
        int offset = 0;
        assert(afields);
        while (afields)    // copy the A_fieldList to Ty_fieldList
        {
            tfieldsNew = checked_malloc(sizeof(*tfieldsNew));
            tfield = checked_malloc(sizeof(*tfield));
            tfieldsNew->head = tfield;
            tfieldsNew->head->name = afields->data->name;
            tfieldsNew->head->offset = offset;
            tfieldsNew->tail = NULL;

            t = S_look(tenv, afields->data->typ);
            if (!t)
            {
                EM_error(afields->data->pos,
                    "Invalid type in record type");
                t = Ty_Nil();
            }
            offset += Tr_getSize(t); // set the offset of the record member
            tfieldsNew->head->ty = t;
            if (tfieldsOld != NULL)
                tfieldsOld->tail = tfieldsNew;
            else
                newt->u.record = tfieldsNew;
            tfieldsOld = tfieldsNew;
            afields = afields->next;
        }
        newt->kind = Ty_record;
        return newt;
    }
    default:
        assert(0);
    }
    return NULL;
}

static bool m_appendInstr = TRUE;
Tr_exp transSeq(A_expList exp, S_table venv, S_table tenv, Tr_level level, Temp_label breakk)
{
    if (!exp)
        return NULL;
    return Tr_seqExp(
            SEM_transExp(venv, tenv, level, exp->data, breakk).exp, 
            transSeq(exp->next, venv, tenv, level, breakk));
}

struct expty SEM_transExp(S_table venv, S_table tenv, Tr_level level, A_exp e, Temp_label breakk)
{
    if (!e)
            return expTy(NULL, Ty_Nil());

    switch(e->kind)
    {
    case A_varExp:
        return SEM_transVar(venv, tenv, level, e->u.var,breakk);
    case A_intExp:
        return expTy(Tr_intExp(e->u.intt),Ty_Int());
    case A_stringExp:
        assert(0);     // TO BE DONE
        return expTy(NULL,Ty_String());
    case A_callExp:
    {
        E_enventry funentry;
        E_enventry f;
        Tr_exp result;
        assert(0);
        // TODO: need to check the post parameter types
        f = S_look(venv, e->u.call.func);
        if (!f)
        {
            EM_error(e->pos, "Unknow function");
            return expTy(NULL, Ty_Nil());
        }
        assert(f->kind == E_funEntry);
        funentry = S_look(venv, e->u.call.func);
        result = Tr_call(venv, tenv, funentry->u.fun.label,
                    level, e->u.call.args, breakk);
        return expTy(result,f->u.fun.result);
    }
    case A_opExp:
    {
        struct expty left = SEM_transExp(venv, tenv, level, e->u.op.left, breakk);
        struct expty right = SEM_transExp(venv,tenv, level, e->u.op.right, breakk);

        switch(e->u.op.oper)
        {
        case(A_plusOp):
        case(A_minusOp):
        case(A_timesOp):
        case(A_divideOp):
        case(A_andOp):     // operators below not implemented yet
        case(A_orOp):
        case(A_lshiftOp):
        case(A_rshiftOp):
        case(A_arshiftOp):
        case(A_xorOp):
        {
            Tr_exp result;
            if (left.ty->kind != Ty_int)
                EM_error(e->u.op.left->pos,
                    "Interger required");
            if (right.ty->kind != Ty_int)
                EM_error(e->u.op.right->pos,
                    "Interger required");
            result = Tr_binOper(e->u.op.oper,
                    left.exp, right.exp);
            return expTy(result, Ty_Int());
        }
        case(A_eqOp):
        case(A_neqOp):
        case(A_ltOp):
        case(A_leOp):
        case(A_gtOp):
        case(A_geOp):
        {
            Tr_exp result;
            if (left.ty->kind != right.ty->kind)
                EM_error(e->u.op.right->pos,
                "Different types while trying to compare");
            result = Tr_condOper(e->u.op.oper, left.exp,
                    right.exp);
            return expTy(result, Ty_Int());
        }

        case(A_selfaddOp):
        {
            Tr_exp result;
            if (left.ty->kind != Ty_int)
                EM_error(e->u.op.left->pos,
                    "Integer required");
            result = Tr_monoOper(e->u.op.oper,
                    left.exp);
            return expTy(result, Ty_Nil());
        }

        }
    }// eof A_opExp
    case A_recordExp:
        assert(0);
        return expTy(NULL, Ty_Nil());
    case A_assignExp:
        {
            Tr_exp result;
            struct expty vt = SEM_transVar(venv, tenv,
                        level, e->u.assign.var, breakk);
            struct expty et = SEM_transExp(venv, tenv,
                        level, e->u.assign.exp, breakk);

            if (vt.ty->kind != et.ty->kind)
                EM_error(e->u.assign.var->pos,
                  "Different types while trying to assign");

            result = Tr_assign(vt.exp, et.exp);
            return expTy(result, Ty_Nil());
        }
    case A_ifExp:
        {
            struct expty testt;
            struct expty thent;
            struct expty elset;
            Tr_exp exp;
            bool backup = m_appendInstr;

            m_appendInstr = FALSE;
            testt = SEM_transExp(venv, tenv,
                        level, e->u.iff.test, breakk);
            
            thent = SEM_transExp(venv, tenv,
                        level, e->u.iff.then, breakk);

            elset = SEM_transExp(venv, tenv,
                        level, e->u.iff.elsee, breakk);
            m_appendInstr = backup;
            

            if (testt.ty->kind != Ty_int)
                EM_error(e->u.iff.test->pos,
                    "Boolean(int) type required");
            if (elset.ty->kind != Ty_nil ||
                thent.ty->kind != Ty_nil )
                EM_error(e->u.iff.elsee->pos,
                    "Invalid type for else section");
            exp = Tr_if(testt.exp, thent.exp,
                    elset.exp);
            return expTy(exp, Ty_Nil());
        }
    case A_whileExp:
        {
            Tr_exp exp;
            Temp_label lend = Temp_newlabel();
            struct expty testt = SEM_transExp(venv, tenv,
                    level, e->u.whilee.test, lend);
            struct expty bodyt = SEM_transExp(venv, tenv,
                    level, e->u.whilee.body, lend);

            if (testt.ty->kind != Ty_int)
                EM_error(e->u.whilee.test->pos,
                    "Boolean(int) type required");
            if (bodyt.ty->kind != Ty_nil)
                EM_error(e->u.whilee.body->pos,
                    "Invalid type for body section");
            exp = Tr_while(testt.exp, bodyt.exp, lend);
            return expTy(exp, Ty_Nil());
        }
    case A_forExp:
        {
            Tr_exp result;
            Temp_label lend = Temp_newlabel();
            struct expty testt = SEM_transExp(venv, tenv,
                    level, e->u.forr.test, NULL);
            struct expty hit = SEM_transExp(venv, tenv,
                    level, e->u.forr.hi, NULL);
            struct expty lot = SEM_transExp(venv, tenv,
                    level, e->u.forr.lo, NULL);
            struct expty body = SEM_transExp(venv, tenv,
                    level, e->u.forr.body, lend);

            if (testt.ty->kind != Ty_int)
                EM_error(e->u.forr.test->pos,
                "Boolean(int) type required");

            result = Tr_for(lot.exp, testt.exp, hit.exp,
                        body.exp, lend);
            return expTy(result, Ty_Nil());
        }
    case A_decExp:
        return SEM_transDec(venv, tenv, level, e->u.dec, breakk);
    case A_seqExp:
    {
        A_expList lptr = e->u.seq;
        Tr_exp exp = NULL;
        struct expty expNew;
        
        if (m_appendInstr)
        {
            do
            {
                expNew = SEM_transExp(venv, tenv, level, lptr->data, breakk);
                Tr_appendStm(expNew.exp);
                lptr = lptr->next;
            } while (lptr);
        }
        else
            exp = transSeq(lptr, venv, tenv, level, breakk);

        return expTy(exp, Ty_Nil());
    }
    case A_asmExp:
        return expTy(Tr_nullExp(), Ty_Nil());
    case A_breakExp:
    {
        Tr_exp result;
        if (breakk == NULL)
            EM_error(e->pos, "Unexpected expression \"break\"");
        result = Tr_break(breakk);
        return expTy(result, Ty_Nil());
    }
    }

    assert(NULL);    // shouldn't reach here
    return expTy(NULL, Ty_Nil());
}// eof SEM_transExp

// translate the exps of the array subscripts
void calArraySub(A_var v, Tr_exp *temp, S_table venv, S_table tenv, 
        Tr_level level)
{
    int i;
    assert(v->u.subscript.size>=1);
    
    for (i=0; i<v->u.subscript.size; i++)
    {
        struct expty et = SEM_transExp(venv, tenv, level, 
                    v->u.subscript.sub[i], NULL);
        temp[i] = et.exp;
        // check the type of the subscription
        if (et.ty != S_look(tenv, S_Symbol("int")))
        {
            EM_error(v->pos, "Integer expression expteced in array subscription");
            return;
        }
    }
    temp[i] = NULL;    
}

// calculate the offset of the var from the base address
// intype: the type of the array var
// vsub:   var subscript
// tsub:   type subscript
struct expty calArrayOffset(Tr_exp base, Ty_ty intype, Tr_exp *vsub)
{
    Tr_exp result = base;
    Ty_ty type = intype;
    int i= 0;
    Ty_subList tsub = type->u.array.size;
    do
    {
        result = Tr_subArrayVar(result, &vsub[i], tsub, Tr_getSize(type->u.array.type));
        i+= type->u.array.dim;
        type = type->u.array.type;
    }while(type->kind == Ty_array);
    return expTy(result, type);
}

// calculate and translate the offset into expty
struct expty transField(S_table venv, S_table tenv, Tr_level level, Temp_label breakk,
    Tr_exp varBase, Ty_ty recordType, A_var v)
{
    Ty_fieldList fields = recordType->u.record;
    bool subMatch = FALSE;
    S_symbol subName;
    A_var vmain, vsub;

    assert(recordType);
    vmain = v->u.field.main;
    vsub = v->u.field.sub;

    switch(vsub->kind)    // Get the name of the sub field
    {
    case A_simpleVar:
        subName = vsub->u.simple;
        break;
    case A_fieldVar:
        assert(vsub->u.field.main->kind != A_fieldVar);
        if (vsub->u.field.main->kind == A_simpleVar)
            subName = vsub->u.field.main->u.simple;
        else
            subName = vsub->u.field.main->u.subscript.name;
        break;
    case A_subscriptVar:
        subName = vsub->u.subscript.name;
        break;
    default:
        assert(0);
    }

    while (fields)    // check if the fields type have such member var
    {
        if (strcmp(S_name(fields->head->name),
            S_name(subName)) == 0)
        {
            subMatch = TRUE;
            break;
        }
        fields = fields->tail;
    }
    if (!subMatch)
    {
        EM_error(v->pos, "Record have not got such member");
        return expTy(Tr_nullExp(), Ty_Nil());
    }

    switch (vsub->kind) 
    {
    case A_subscriptVar: // sub var is a array, for example: a.b[i]
    {
        Tr_exp temp[MAX_LINK_SIZE];
        Tr_exp base;

        if (fields->head->ty->kind != Ty_array)
        {
            EM_error(v->pos, "Variable used is not a array var");
            return expTy(Tr_nullExp(), Ty_Nil());
        }
        // calculate the base address
        if (fields->head->offset == 0)
            base = varBase;
        else
            base = Tr_binOper(A_plusOp,     
                    varBase,
                    Tr_intExp(fields->head->offset));
        
        if (v->u.field.main->kind == A_subscriptVar)
        {
            // Both main and sub var are array
            // for example: a[i].b[j]
            Tr_exp temp2[MAX_LINK_SIZE];
            calArraySub(v->u.field.main, temp2, venv, tenv, level);
            base = calArrayOffset(base, fields->head->ty, temp2).exp;
        }

        // add the array offset to the base address
        calArraySub(v->u.field.sub, temp, venv, tenv, level);
        return calArrayOffset(
                Tr_binOper(A_plusOp,
                    base, 
                    Tr_intExp(fields->head->offset)),
                fields->head->ty, temp);
    }
    case A_simpleVar:
    {
        // result = varBase + offset
        Tr_exp exp = Tr_binOper(A_plusOp,
                varBase,
                Tr_intExp(fields->head->offset));

        return expTy(exp,fields->head->ty);
    }
    case A_fieldVar:
    {
        if (fields->head->ty->kind == Ty_array)    
        {
            // the sub var is field var, while the main var 
            // of the sub var is a array    
            // for example: a.b[i].c
            Tr_exp subs[MAX_LINK_SIZE];
            Tr_exp base;
            if (vsub->u.field.main->kind == A_simpleVar)
            {
                // main var of the sub can only be simpleVar and fieldVar,
                // we only need to confirm it's not a simple var 
                EM_error(v->pos, "Subscript variable required");
                return expTy(NULL, Ty_Nil());
            } 
            // calculate the exps in the subscript and add the result 
            // to varBase
            calArraySub(v->u.field.sub->u.field.main, subs, venv, tenv, level);
            base = calArrayOffset(Tr_intExp(fields->head->offset), 
                    fields->head->ty, subs).exp;
            base = Tr_binOper(A_plusOp, varBase, base);
            return transField(venv, tenv, level, breakk,
                base, fields->head->ty->u.array.type, v->u.field.sub);
        }
        else
        {
            Tr_exp base = Tr_binOper(T_plus,
                    varBase,
                    Tr_intExp(fields->head->offset));
            return transField(venv, tenv, level, breakk,
                base, fields->head->ty, v->u.field.sub);
        }
    }
    default:
        assert(0);
        return expTy(Tr_nullExp(), Ty_Nil());
    }
}

struct expty SEM_transVar(S_table venv, S_table tenv, Tr_level level, A_var v, Temp_label breakk)
{
    switch(v->kind)
    {
    case A_simpleVar:
    {
        Ty_ty vt;
        E_enventry e = S_look(venv, v->u.simple);
        if (!e)
        {
            EM_error(v->pos, "Unknow variable");
            return expTy(NULL, Ty_Nil());
        }
        assert(e->kind == E_varEntry);
        vt = e->u.var.ty;
        return expTy(Tr_simpleVar(e->u.var.access,level),
                actualType(vt));
    }
    case A_fieldVar:
    {
        Tr_exp base;
        A_var vmain = v->u.field.main;
        E_enventry entry = NULL;    // entry of the main part
        struct expty e;            // the translate result of the main part
        Ty_ty ty;            // type of the main part of the fieldVar

        // the main var of a fieldVar can only be simpleVar or subscriptVar
        if (v->u.field.main->kind == A_simpleVar)
            entry = S_look(venv, vmain->u.simple);
        else
            entry = S_look(venv, vmain->u.subscript.name);
        
        e = SEM_transVar(venv, tenv, level, vmain, breakk);
        ty = e.ty;
        if (!ty)
        {
            EM_error(v->pos, "Unknow record type");
            return (expTy(Tr_nullExp(), Ty_Nil()));
        }
        if (ty->kind != Ty_record)
        {
            EM_error(v->pos, "Record type expected");
            return (expTy(Tr_nullExp(), Ty_Nil()));
        }
        // get the address of the var
        base = Tr_varBase(entry->u.var.access, level);
        // e = base + offset
        e = transField(venv, tenv, level, breakk, base, ty, v);
        e.exp = Tr_memVar(e.exp);
        return e;
    
    }
    case A_subscriptVar:
    {    
        E_enventry e;
        Tr_exp temp[MAX_LINK_SIZE];
        struct expty result;

        assert(v->u.subscript.size>=1);

        // check the type of the var
        e = S_look(venv, v->u.subscript.name);
        if (!e || e->u.var.ty->kind == Ty_nil)
        {
            EM_error(v->pos, "Undefined subscripter variable");
            return expTy(Tr_nullExp(), Ty_Nil());
        }

        // Translate the subscript exps and save it in temp
        calArraySub(v, temp, venv, tenv, level); 

        result = calArrayOffset(Tr_varBase(e->u.var.access, level),
                e->u.var.ty, temp);
        result.exp = Tr_memVar(result.exp);
        return result;
    }
    }
    assert(0);    // shouldn't reach here
    return expTy(NULL, Ty_Nil());
}

Ty_tyList makeFormalTyList(S_table tenv, A_fieldList params, int pos)
{
    Ty_tyList pold = NULL, pnew = 0;
    A_fieldList pParams;

    for (pParams = params; pParams; pParams = pParams->next)
    {
        pnew = checked_malloc(sizeof(*pnew));
        pnew->head = S_look(tenv, pParams->data->typ);
        if (!pnew->head)
            EM_error(pos, "Unknow to function parameter type");

        pnew->tail = pold;
        pold = pnew;
    }

    return pnew;
}

struct expty SEM_transDec(S_table venv, S_table tenv,
                Tr_level level, A_dec d, Temp_label breakk)
{
    switch(d->kind)
    {
    case A_funDec:
    {
        Ty_ty resultt = S_look(tenv, d->u.fun.result);
        Ty_tyList formalt = NULL;
        E_enventry funcname = NULL;
        if (!resultt)
            EM_error(d->pos, "Unknow function return type");
        if (d->u.fun.params!=NULL)
            formalt = makeFormalTyList(tenv, d->u.fun.params,
                            d->pos);
        funcname = S_look(venv, d->u.fun.name);
        if (d->u.fun.body == NULL)    // This is only a declaration
        {
            if (funcname)
                EM_error(d->pos, "Redeclaration of function");

            S_enter(venv, d->u.fun.name,
                E_FunEntry(    level,
                        Temp_newlabel(),
                        formalt,
                        resultt));
            if (d->u.fun.body == NULL)    // a declare only
                return expTy(Tr_nullExp(), Ty_Nil());
        }
        else                // This is a implementation
        {
            U_boolList boolnew = NULL, boolold = NULL;
            Tr_level newlevel;
            struct expty body;
            Tr_exp funexp;
            if (funcname)
            {
                if (funcname->u.fun.imped)
                    EM_error(d->pos, "Dumplicated function implementation");
            }
            else
                S_enter(venv, d->u.fun.name,
                    E_FunEntry(    level,
                            Temp_newlabel(),
                            formalt,
                            resultt));
            S_beginScope(venv);
            
            if (formalt)     // if there's some parameters
            {
                A_fieldList l;
                Ty_tyList t;
                for (l = d->u.fun.params, t = formalt;
                l;
                l = l->next, t=t->tail)
                {
                    E_venter(venv, l->data->name, E_VarEntry(
                        Tr_allocLocal(level, l->data->escape, 
                            Tr_getSize(t->head)), t->head));
                    boolnew = U_BoolList(l->data->escape, boolold);
                    boolold = boolnew;
                }
            };
            newlevel = Tr_newLevel(level, Temp_newlabel(), boolnew);
            body = SEM_transExp(venv, tenv, newlevel, d->u.fun.body, NULL);
            funexp = Tr_funcDec(d->u.fun.name, body.exp, level);
            S_endScope(venv);
            return expTy(funexp, Ty_Nil());
        }
    }
    case A_varDec:
        {
        Tr_access acc;
        Tr_exp result;
        struct expty einit;
        Ty_ty ve;
        einit.exp = (Tr_exp)NULL;
        einit.ty = (Ty_ty)NULL;
        ve = S_look(tenv, d->u.var.typ);
        if (!ve)
        {
            EM_error(d->pos,
                "Unknow type in var declaration");
            return expTy(NULL, Ty_Nil());
        }
        if (d->u.var.init != NULL)
        {     // check and translate the init exp
            einit = SEM_transExp(venv, tenv, level, d->u.var.init, breakk);
            if (ve != einit.ty)
                EM_error(d->pos,
                    "Different types among var and init expression"
                    "while declaring");
        }
        else
            einit.exp = NULL;

        acc = Tr_allocLocal(level, d->u.var.escape, Tr_getSize(ve));
        E_venter(venv, d->u.var.var, E_VarEntry(acc, ve));
        result = Tr_varDec(acc, einit.exp, level);

        return expTy(result, Ty_Nil());
    }
    case A_typeDec:
        {
        Ty_ty type = SEM_transTy(tenv, d->u.ty);
        switch(d->u.ty->kind)
        {
        case A_arrayTy:
            E_tenter(tenv, d->u.ty->u.array.name, type);
            break;
        case A_recordTy:
            E_tenter(tenv, d->u.ty->u.record.name, type);
            break;
        case A_nameTy:
            E_tenter(tenv, d->u.ty->u.name.name, type);
            break;
        default:
            assert(0);
        }
        
        return expTy(Tr_nullExp(), type);
        }
    }
    assert(0);
    return expTy(NULL, Ty_Nil());
}
